Hi,
Into my app I will run the launchctl to load/unload the daemon, so I have how to control it properly.
About shipping an uninstaller, as I never saw such thing (i am new user for macOs, even working with Xcode since 2020 this is not my main computer) so I will think about.
Thanks for your tip!!
Post
Replies
Boosts
Views
Activity
Hi Quinn,
Well, maybe yes, my "daemon", allow me :) is started by launchd, the plist is correctly set to /library/launchdaemons, system restart and it will start with the OS. For close I will launchctl load or unload to stop. And yes, Packages is a 3rd party app to create installer package (free).
That said all seems to work but I am not sure on how it would work when uninstalling. Today I will prepare the installer and give a try but I dont see a way to remove it, unless MacOS is very clever and somehow knows that once we move the app to trash it will also move the daeamon...
Thanks
As everyone else I am too unable to get the SSID information with Sonoma but it is still not clear to me if when using the location service it should finally work.
Which type of func is it ? If async, it could be a concurrency issue.
Yes, it is async and after reading some of your questions I refactored some parts and seems to be working now. Anyway it's one of the expected weird behaviors when not dealing well with threads.
Thank you guys!
I will paste the full code, this is from Playground, now running on playground it won't separate the array, so I guess that can be related to the way I am doing in the real code, there it is called inside another function, so I think that I will enclosure it a thread and see what happens. The code below works fine.
Also, I know I can simply use wifiList.removeAll() instead of wifiList.removeAll(keepingCapacity: false), but as someone with little knowledgement in Swift I use that way to force me to learn about other options.
import Foundation
import CoreWLAN
class Discovery {
var currentInterface: CWInterface
var interfacesNames: [String] = []
var networks: Set<CWNetwork> = []
// Failable init using default interface
init?() {
if let defaultInterface = CWWiFiClient.shared().interface(),
let name = defaultInterface.interfaceName {
self.currentInterface = defaultInterface
self.interfacesNames.append(name)
self.findNetworks()
} else {
return nil
}
}
init(interfaceWithName name: String) {
self.currentInterface = CWWiFiClient().interface(withName: name)!
self.interfacesNames.append(name)
self.findNetworks()
}
// Fetch detectable WIFI networks
func findNetworks() {
do {
self.networks = try currentInterface.scanForNetworks(withSSID: nil)
} catch let error as NSError {
print("Error: \(error.localizedDescription)")
}
}
}
struct Wifi: Codable {
var SSID: String
var bSSID: String
var Channel: Int
var Band: String
var Width: String
var noise: Int
var rssi: Int
var qualitySNR: Int
var isConnected: Bool
init() {
SSID = ""
bSSID = "00:00:00:00:00:00"
Channel = 0
Band = ""
Width = ""
rssi = 0
noise = 0
qualitySNR = 0
isConnected = false
}
}
var wifiList: [Wifi] = []
func getWifiNetworks() {
var connectedNetwork = ""
wifiList.removeAll(keepingCapacity: false)
if let discovery = Discovery() {
var wifi: Wifi
let connectedNetwork = CWWiFiClient.shared().interface()!.ssid()!
for network in discovery.networks {
wifi = Wifi.init()
wifi.SSID = network.ssid?.description ?? ""
wifi.isConnected = wifi.SSID == connectedNetwork
wifi.bSSID = network.bssid?.description ?? "00:00:00:00:00:00"
wifi.Channel = network.wlanChannel?.channelNumber ?? 0
wifi.rssi = network.rssiValue
wifi.noise = network.noiseMeasurement
wifi.qualitySNR = wifi.rssi - wifi.noise
switch network.wlanChannel?.channelBand {
case .bandUnknown:
wifi.Band = "Unknown"
case .band2GHz:
wifi.Band = "2Ghz"
case .band5GHz:
wifi.Band = "5Ghz"
case .band6GHz:
wifi.Band = "6Ghz"
case .none:
wifi.Band = "Unknown"
case .some(_):
wifi.Band = "Unknown"
}
switch network.wlanChannel?.channelWidth {
case .width160MHz:
wifi.Width = "160MHz"
case .none:
wifi.Width = "none"
case .some(.widthUnknown):
wifi.Width = "Unknown"
case .some(.width20MHz):
wifi.Width = "20MHz"
case .some(.width40MHz):
wifi.Width = "40MHz"
case .some(.width80MHz):
wifi.Width = "80MHz"
case .some(_):
wifi.Width = "Unknown"
}
wifi.rssi = network.rssiValue
wifiList.insert(wifi, at: 0)
}
}
}
func convertWifiListToString() -> String {
let encoder = JSONEncoder()
let data = try! encoder.encode(wifiList)
return String(data: data, encoding: .utf8)!
}
getWifiNetworks()
let wifiListJson = convertWifiListToString()
print(wifiListJson)
Ok, this is my struct and the list, the real one:
struct Wifi: Codable {
var SSID: String
var bSSID: String
var Channel: Int
var Band: String
var Width: String
var noise: Int
var rssi: Int
var qualitySNR: Int
var isConnected: Bool
init() {
SSID = ""
bSSID = "00:00:00:00:00:00"
Channel = 0
Band = ""
Width = ""
rssi = 0
noise = 0
qualitySNR = 0
isConnected = false
}
}
var wifiList: [Wifi] = []
Here is how I do populate it:
wifiList.removeAll(keepingCapacity: false)
//... get all stuff from the network and insert into the list
// wifi is declared as wifi = Wifi.init()
wifiList.insert(wifi, at: 0)
After the list if completed I convert it string, as I showed before:
func convertToJSONString() -> String {
let encoder = JSONEncoder()
let data = try! encoder.encode(wifiList)
return String(data: data, encoding: .utf8)!
}
What I get:
[{"bSSID":"00:00:00:00:00:00","Band":"2Ghz","rssi":-77,"SSID":"CLARO_2GA37FE4","Channel":6,"isConnected":false,"Width":"40MHz","noise":76,"qualitySNR":-153},{"bSSID":"00:00:00:00:00:00","Band":"2Ghz","rssi":-74,"SSID":"2.4G DEMOLIDOR HELMAR LUAN","Channel":8,"isConnected":false,"Width":"40MHz","noise":76,"qualitySNR":-150},{"bSSID":"00:00:00:00:00:00","Band":"5Ghz","rssi":-88,"SSID":"TP-LINK_9562_5G","Channel":157,"isConnected":false,"Width":"80MHz","noise":76,"qualitySNR":-164},{"bSSID":"00:00:00:00:00:00","Band":"5Ghz","rssi":-77,"SSID":"AP 71 A-5G","Channel":52,"isConnected":false,"Width":"80MHz","noise":76,"qualitySNR":-153},{"bSSID":"00:00:00:00:00:00","Band":"5Ghz","rssi":-48,"SSID":"XIMA","Channel":157,"isConnected":true,"Width":"80MHz","noise":76,"qualitySNR":-124},{"bSSID":"00:00:00:00:00:00","Band":"5Ghz","rssi":-83,"SSID":"5.G DEMOLIDOR HELMAR LUAN","Channel":48,"isConnected":false,"Width":"80MHz","noise":76,"qualitySNR":-159},{"bSSID":"00:00:00:00:00:00","Band":"2Ghz","rssi":-61,"SSID":"LIMA_2G","Channel":10,"isConnected":false,"Width":"40MHz","noise":76,"qualitySNR":-137},{"bSSID":"00:00:00:00:00:00","Band":"2Ghz","rssi":-76,"SSID":"Net-Virtua-9095-2.4G","Channel":1,"isConnected":false,"Width":"40MHz","noise":76,"qualitySNR":-152},{"bSSID":"00:00:00:00:00:00","Band":"2Ghz","rssi":-84,"SSID":"#NET-CLARO-WIFI","Channel":1,"isConnected":false,"Width":"40MHz","noise":76,"qualitySNR":-160},{"bSSID":"00:00:00:00:00:00","Band":"2Ghz","rssi":-91,"SSID":"Renato.2G","Channel":11,"isConnected":false,"Width":"40MHz","noise":76,"qualitySNR":-167},{"bSSID":"00:00:00:00:00:00","Band":"5Ghz","rssi":-78,"SSID":"AP 71 A","Channel":52,"isConnected":false,"Width":"80MHz","noise":76,"qualitySNR":-154},{"bSSID":"00:00:00:00:00:00","Band":"2Ghz","rssi":-85,"SSID":"Vila Mariana","Channel":1,"isConnected":false,"Width":"40MHz","noise":76,"qualitySNR":-161}]
[{"bSSID":"00:00:00:00:00:00","Band":"5Ghz","rssi":-52,"SSID":"XIMA","Channel":157,"isConnected":false,"Width":"80MHz","noise":80,"qualitySNR":-132}]
In this example, the last item shouldn't be "out" of the array. The result shows like 2 arrays and I expect to have only one.
Yeah, I ate the 1st line:
func listDir(dir: String) {
}
And I use to try some functions firstly in Playground before apply into the code. I will make a small test program to do that and let you know, thanks again.
I can use who -b but it will be the same to last shutdown. I am seeking for a API level to use with Swift.
Adding something else to this thread. Here a sample code that I use in my original code (not exactly this way, but the main part is quite close)
I started a new Command Line Tool with Xcode 14.2, i notice that there's no more config.plist so I take from my app to ensure that NSLocationAlwaysUsageDescription, NSLocationUsageDescription and NSLocationWhenInUseUsageDescription are there - config.plist attached here too. No luck with Ventura so far.
config.plist.txt
import Foundation
import CoreLocation
struct TCoordinates {
var latitude: Double = 0
var longitude: Double = 0
var altitude: Double = 0
var course: Double = 0
var precision: Double = 0
}
let locationManager = CLLocationManager()
var coordinates: TCoordinates
coordinates = TCoordinates.init()
func getCoordinates(locationManager: CLLocationManager) -> TCoordinates {
var ret = TCoordinates.init()
ret.latitude = locationManager.location?.coordinate.latitude ?? 0
ret.longitude = locationManager.location?.coordinate.longitude ?? 0
ret.altitude = locationManager.location?.altitude ?? 0
ret.course = locationManager.location?.course ?? 0
ret.precision = locationManager.location?.horizontalAccuracy ?? 0
return ret
}
private func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case .notDetermined:
// If status has not yet been determied, ask for authorization
manager.requestWhenInUseAuthorization()
break
case .authorizedWhenInUse:
// If authorized when in use
manager.startUpdatingLocation()
break
case .authorizedAlways:
// If always authorized
manager.startUpdatingLocation()
break
case .restricted:
// If restricted by e.g. parental controls. User can't enable Location Services
break
case .denied:
// If user denied your app access to Location Services, but can grant access from Settings.app
break
default:
break
}
}
func getGeoLocation() {
coordinates = getCoordinates(locationManager: locationManager)
if CLLocationManager.locationServicesEnabled() {
let authorizationStatus: CLAuthorizationStatus
if #available(iOS 14, *) {
authorizationStatus = locationManager.authorizationStatus
} else {
authorizationStatus = CLLocationManager.authorizationStatus()
}
switch authorizationStatus {
case .restricted, .notDetermined:
locationManager.requestAlwaysAuthorization()
print("Restricted")
exit(0)
case .denied:
print("Denied")
case .authorizedAlways, .authorizedWhenInUse:
print("Authorized")
case .authorized:
print("Authorized")
@unknown default:
print("Unavailable")
}
}
}
/* -------- */
locationManager.startUpdatingLocation()
getGeoLocation()
autoreleasepool {
RunLoop.main.run()
}
Hi Claude, sorry and thanks for you patience!!
I FIXED the issue!
Could you show how you get data ?
The data is typed in the user interface configuration form. I made a kind of helper to use the SQLite, so it can read and update tables and there was one of the issues.
With this helper I can handle the database and table as easy as:
var db: OpaquePointer?
var sqlQuery: sqliteQuery
var sqlText: OpaquePointer? = nil
let fileURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
.appendingPathComponent("/mydatabase.sqlite")
let sqlText = "INSERT INTO TABLE (user, password, status, date) VALUES (:user, :password, :status, :date);"
if sqliteOpenDatabase(DatabaseURL: fileURL, Database: &db) == SQLITE_OK {
sqlQuery = sqliteQuery(Database: &db, Statement: &sqlText, Query: queryString)
sqlQuery.ParamByName(Param: "user", ValueType: .string, Value: "Username" )
sqlQuery.ParamByName(Param: "password", ValueType: .string, Value: "123456" )
sqlQuery.ParamByName(Param: "status", ValueType: .string, Value: "valid" )
sqlQuery.ParamByName(Param: "date", ValueType: .date , Value: Date() )
sqlQuery.ExecSQL()
print(sqlQuery.ParamByName(Param: "user"))
}
Where do data come from ?
The data comes from a SQLite table
What is exactly in value ? Is it an optional ?
Think about saving a email field into a table. You should open the table and read something like "myname@site. com" (this forum dont allow type emails, so theres no space here, its an example) but once you read it (any program including and besides yourself) you would get as email a weird string as I was tolding: "Optional("myname@site. com")" and that messed up with everything.
To fix that I had to dig into the code and change all old Optional string convertions, even things like String(cString: value) are depreciated . Things such:
return anyData == nil ? "" : anyData as! String I had to change to return String(describing: anyData)
Also I had to perform a lot of changes in the ancient code. Again thanks for the support and patience!
I figure out the issue but still unable to fix it.
Firstly, I made it stop working with me by replicating all the steps the user do.
Once the data is read from the SQLite table from a field type string (text typed in SQLite) using sql_column_text() function the result itself contains literal text of "Optional"and will break everything.
real code when I am attempting to fix, reading user department:
let stringValue = String(cString: sqlite3_column_text(Statement, index)) // attempt
This will show in the debugger as "Optional(\"Dev team\")" and the UI where the user changes the information, also shows exactly that way! All of this trouble after updating to XCode 5 (original was done with version 4). I opened the database using a 3rd party software and these characters are there!
Here how I read the data:
struct ColumnValue {
var Column: String
var Value: Any? // Need to be any because will support int, float, string at least.. using Any or Any makes no difference
}
var ResultSet: [ColumnValue]
var columnValue: ColumnValue
// ... after open database, now scroll the table
// simplified loop
while true {
for index in 0..<sqlite3_column_count(Statement) {
let column = String(cString: sqlite3_column_origin_name(Statement, index)!)
let type = sqlite3_column_type(Statement, index)
switch type {
// if column is type of string (text)
case SQLITE_TEXT, SQLITE3_TEXT:
let stringValue = String(cString: sqlite3_column_text(Statement, index)!)
columnValue = ColumnValue(Column: column, Value: stringValue)
default:
columnValue = ColumnValue(Column: column, Value: nilAny)
}
ResultSet.insert(columnValue, at: 0)
self.RowCount += 1
if sqlite3_step(Statement) == SQLITE_DONE { break }
}
}
All the issue is because stringValue is storing literal characters of the optional type. The information read should be a plain string like "Dev team", as I worked until now, and not that weird string "Optional(\"Dev team\")"
Hi man!
Well config is a global var, after this lines bellow I initiate var config = Config.init()
And Yes, loadConfiguration is populating the config, exatly like:
// after read the host field in the config table this is what I do:
config.endpoint = (data.Value as! String).trimmingCharacters(in: .whitespacesAndNewlines)
I also tried:
config.endpoint = String((data.Value as! String).trimmingCharacters(in: .whitespacesAndNewlines))
The data contains the value fetched from the table. Because of data.Value as! String I guess that the original string is an optional. How could I "fix" that? Or why would the program fail that way?
Here the real code, part of it:
// part of the struct
struct Config {
var endpoint: String
var endpoint_scheme: String
var endpoint_path: String
var endpoint_port: Int32
init() {
endpoint = ""
endpoint_path = ""
endpoint_port = 0
endpoint_scheme = ""
}
}
// part of the function that would sent data
func submitJson(jsonData: String, scheme: String, host: String, path: String, port: Int32, completion:((Error?) -> Void)?) -> Bool {
var urlComponents = URLComponents()
urlComponents.scheme = scheme
urlComponents.host = host
urlComponents.path = path
urlComponents.port = Int(port)
print(host) // this is just when I was trying to figure out what "invalid ' characters the app was complaining
guard let url = urlComponents.url else {
displayInfo(info: "URL configuration is invalid")
return false
}
// the rest of the code goes below, when it will fail to post the data due that invalid characters
}
// other place I should use:
var config = Config.init()
loadConfiguration() // somewhere it reads the SQLite
// and when need I will send some stuff based on the configuration
result = submitJson(jsonData: jsonDataStr, scheme: config.endpoint_scheme, host: config.endpoint, path: config.endpoint_path, port: config.endpoint_port)
Again, this works with my computer. That print won't display "Optional("anyendpoint.com")" but as an example in my computer it prints simple plain text "anyendpoint.com"
Thanks again
Actually I think I can add this, which is how the var passed to the function get its value:
myStruct.theString = (data.Value as! String).trimmingCharacters(in: .whitespacesAndNewlines)
The data.Value here is a string I read from a SQLite table. So when running
myStruct.theString = (data.Value as! String).trimmingCharacters(in: .whitespacesAndNewlines)
func(param: myStruct.theString)
Results output as:
Optional("Test")
This gives an error of invalid characters at my client computer,
but running the same code with my computer:
Test
There isn't mention to "Optional" and runs flawlessly
Hi again
This is an app, compiled and running on the terminal. About the use of the function its just like test(param: str).
My real program is passing a struct property to the function, like test(param: myStruct.theString) the struct defines "theString" as String type. I really don't have a lot to show.